package com.pdg.android.splorr.data;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.pdg.android.splorr.Utilities;
import com.pdg.android.splorr.data.baseclasses.DataClient;
import com.pdg.android.splorr.data.enums.ArmorType;
import com.pdg.android.splorr.data.enums.DoorState;
import com.pdg.android.splorr.data.enums.ItemType;
import com.pdg.android.splorr.data.enums.LockDetail;
import com.pdg.android.splorr.data.enums.LockMaterial;
import com.pdg.android.splorr.data.enums.RoomDirection;
import com.pdg.android.splorr.data.enums.RoomState;
import com.pdg.android.splorr.data.enums.WeaponType;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

public class Maze extends DataClient<Game>{

	private static final String SUBKEY_WIDTH = "width";
	private static final String SUBKEY_HEIGHT = "height";
	private static final int DEFAULT_WIDTH = 0;
	private static final int DEFAULT_HEIGHT = 0;
	private static final String KEY_BASE = "maze_";
	private List<Room> roomData = new ArrayList<Room>();
	private int width;
	private int height;
	
	public Maze(Game theData) {
		super(theData);
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onLoad(SharedPreferences thePreferences) {
		setWidth(thePreferences.getInt(getSubKey(SUBKEY_WIDTH), DEFAULT_WIDTH));
		setHeight(thePreferences.getInt(getSubKey(SUBKEY_HEIGHT), DEFAULT_HEIGHT));
		for(int x=0;x<getWidth();++x){
			for(int y=0;y<getHeight();++y){
				getRoom(x,y).load(thePreferences);
			}
		}
	}

	@Override
	protected void onCommit(Editor theEditor) {
		theEditor.putInt(getSubKey(SUBKEY_WIDTH), getWidth());
		theEditor.putInt(getSubKey(SUBKEY_HEIGHT), getHeight());
		for(int x=0;x<getWidth();++x){
			for(int y=0;y<getHeight();++y){
				getRoom(x,y).commit(theEditor);
			}
		}
	}

	private String getSubKey(String theSubKey) {
		return KEY_BASE + theSubKey;
	}

	@Override
	public Context getContext() {
		return getData().getContext();
	}

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
		rightSizeRoomList();
	}

	private void rightSizeRoomList() {
		while(getRooms().size()>getRoomCount()){
			getRooms().remove(getRooms().size()-1);
		}
		while(getRooms().size()<getRoomCount()){
			getRooms().add(new Room(this));
		}
		refreshRoomXYs();
	}

	private void refreshRoomXYs() {
		for(int x = 0;x<getWidth();++x){
			for(int y=0;y<getHeight();++y){
				Room theRoom = getRoom(x,y);
				theRoom.setX(x);
				theRoom.setY(y);
			}
		}
	}

	public Room getRoom(int x, int y) {
		if(x<0||y<0||x>=getWidth()||y>=getHeight()) return null;
		return getRooms().get(x+y*getWidth());
	}

	private List<Room> getRooms() {
		return roomData;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
		rightSizeRoomList();
	}
	
	public int getRoomCount(){
		return getWidth()*getHeight();
	}
	
	private class LockMaterialDetail{
		private LockMaterial material;
		private LockDetail detail;
		public LockMaterialDetail(LockMaterial theMaterial,LockDetail theDetail){
			setMaterial(theMaterial);
			setDetail(theDetail);
		}
		public LockMaterial getMaterial() {
			return material;
		}
		public void setMaterial(LockMaterial material) {
			this.material = material;
		}
		public LockDetail getDetail() {
			return detail;
		}
		public void setDetail(LockDetail detail) {
			this.detail = detail;
		}
		public boolean isSameAs(LockMaterialDetail other){
			return getMaterial()==other.getMaterial() && getDetail()==other.getDetail();
		}
	}

	public void newGame(int theWidth,int theHeight) {
		setWidth(theWidth);
		setHeight(theHeight);
		//STEP ONE: clear out the maze, prep for maze generation
		Set<Room> theOutside = new HashSet<Room>();
		for(int x = 0;x<getWidth();++x){
			for(int y=0;y<getHeight();++y){
				Room theRoom = getRoom(x,y);
				theRoom.clearData();
				theOutside.add(theRoom);
			}
		}
		Set<Room> theInside = new HashSet<Room>();
		Set<Room> theEdge = new HashSet<Room>();
		//STEP TWO: select a room to start generating with (not necessarily the starting point of the maze)
		Room theRoom = getRoom(Utilities.roll(getWidth()),Utilities.roll(getHeight()));
		theInside.add(theRoom);
		theOutside.remove(theRoom);
		for(RoomDirection theDirection:RoomDirection.values()){
			Room theNextRoom = theRoom.getNeighbor(theDirection);
			if(theNextRoom==null) continue;
			if(theOutside.contains(theNextRoom)){
				theOutside.remove(theNextRoom);
				theEdge.add(theNextRoom);
			}
		}
		//STEP THREE: while the maze has not been completely generated, add rooms
		while(theEdge.size()>0){
			theRoom = Utilities.randomValueFromSet(theEdge);
			theEdge.remove(theRoom);
			theInside.add(theRoom);
			Set<RoomDirection> theDirections = new HashSet<RoomDirection>();
			for(RoomDirection theDirection:RoomDirection.values()){
				Room theNextRoom = theRoom.getNeighbor(theDirection);
				if(theNextRoom==null) continue;
				if(theInside.contains(theNextRoom)){
					theDirections.add(theDirection);
				}
				if(theOutside.contains(theNextRoom)){
					theOutside.remove(theNextRoom);
					theEdge.add(theNextRoom);
				}
			}
			RoomDirection theDirection = Utilities.randomValueFromSet(theDirections);
			Room theNextRoom = theRoom.getNeighbor(theDirection);
			theRoom.getDoor(theDirection).setDoorState(DoorState.PASSAGEWAY);
			theNextRoom.getDoor(theDirection.opposite()).setDoorState(DoorState.PASSAGEWAY);
		}
		//STEP FOUR: annotate room specials
		//4A - annotate dead ends
		Iterator<Room> iterator = getRooms().iterator();
		theOutside.clear();
		theInside.clear();
		while(iterator.hasNext()){
			theRoom = iterator.next();
			if(theRoom.getPassableCount()==1){
				theRoom.setRoomState(RoomState.DEAD_END);
				theOutside.add(theRoom);
			}else{
				theRoom.setRoomState(RoomState.NONE);
				theInside.add(theRoom);
			}
		}
		//4B - choose entrance from the non-dead ends
		do{
			theRoom = Utilities.randomValueFromSet(theInside);
		}while(theRoom.getRoomState()!=RoomState.NONE);
		theRoom.setRoomState(RoomState.ENTRANCE);
		//4C - choose exit from the dead ends
		do{
			theRoom = Utilities.randomValueFromSet(theOutside);
		}while(theRoom.getRoomState()!=RoomState.DEAD_END);
		theRoom.setRoomState(RoomState.EXIT);
		//4D - choose the shop from the non-dead ends
		do{
			theRoom = Utilities.randomValueFromSet(theInside);
		}while(theRoom.getRoomState()!=RoomState.NONE);
		theRoom.setRoomState(RoomState.SHOPPE);
		//4E - determine chamber or hallway
		iterator = getRooms().iterator();
		while(iterator.hasNext()){
			theRoom = iterator.next();
			theRoom.setChamber(getData().getUtilities().generateChamberValue(theRoom.getPassableCount()));
		}
		//STEP FIVE: lock doors
		//this happens at dead ends, which we already have in theOutside
		//5A - add doors
		List<LockMaterialDetail> theLocks = new ArrayList<LockMaterialDetail>();
		iterator = theOutside.iterator();
		while(iterator.hasNext()){
			theRoom = iterator.next();
			for(RoomDirection theDirection: RoomDirection.values()){
				if(theRoom.getDoor(theDirection).getDoorState().isPassable()){
					theRoom.getDoor(theDirection).setDoorState(DoorState.LOCKED_DOOR);
					theRoom.getNeighbor(theDirection).getDoor(theDirection.opposite()).setDoorState(DoorState.LOCKED_DOOR);
					boolean found;
					LockMaterialDetail theCandidate;
					do{
						found = false;
						theCandidate = new LockMaterialDetail(Utilities.randomLockMaterial(),Utilities.randomLockDetail());
						Iterator<LockMaterialDetail> lockIterator = theLocks.iterator();
						while(lockIterator.hasNext()){
							LockMaterialDetail theLock = lockIterator.next();
							if(theLock.isSameAs(theCandidate)){
								found=true;
								break;
							}
						}
					}while(found);
					theRoom.getDoor(theDirection).setLockMaterial(theCandidate.getMaterial());
					theRoom.getDoor(theDirection).setLockDetail(theCandidate.getDetail());
					theRoom.getNeighbor(theDirection).getDoor(theDirection.opposite()).setLockMaterial(theCandidate.getMaterial());
					theRoom.getNeighbor(theDirection).getDoor(theDirection.opposite()).setLockDetail(theCandidate.getDetail());
					theLocks.add(theCandidate);
				}
			}
		}
		//5B - add keys
		for(LockMaterialDetail theLock:theLocks){
			//find a non-dead end room with nothing special
			do{
				theRoom = Utilities.randomValueFromSet(theInside);
			}while(theRoom.getRoomState()!=RoomState.NONE);
			//place the key in the room
			RoomItem theItem = new RoomItem(theRoom);
			theRoom.addItem(theItem);
			theItem.setType(ItemType.KEY);
			theItem.setLockMaterial(theLock.getMaterial());
			theItem.setLockDetail(theLock.getDetail());
			theItem.setHidden(Utilities.rollForHiddenItem());
		}
		//Step 6: Finalize doors
		//6A - passageways to doors
		iterator = getRooms().iterator();
		while(iterator.hasNext()){
			theRoom = iterator.next();
			for(RoomDirection theDirection:RoomDirection.values()){
				Door theDoor = theRoom.getDoor(theDirection);
				if(theDoor.getDoorState()==DoorState.PASSAGEWAY){
					if(Utilities.rollForDoor()){
						theDoor.setDoorState(DoorState.CLOSED_DOOR);
						theRoom.getNeighbor(theDirection).getDoor(theDirection.opposite()).setDoorState(DoorState.CLOSED_DOOR);
					}
				}
			}
		}
		//6B - hide doors
		iterator = getRooms().iterator();
		while(iterator.hasNext()){
			theRoom = iterator.next();
			for(RoomDirection theDirection:RoomDirection.values()){
				Door theDoor = theRoom.getDoor(theDirection);
				if(theDoor.getDoorState().canHide()){
					if(Utilities.rollForHiddenDoor()){
						DoorState theNewDoorState =theDoor.getDoorState().hide(); 
						theDoor.setDoorState(theNewDoorState);
						theRoom.getNeighbor(theDirection).getDoor(theDirection.opposite()).setDoorState(theNewDoorState);
					}
				}
			}
		}
		//Step 7: Place Items
		//7A - armor
		for(ArmorType armorType: ArmorType.values()){
			int theItemCount = (int)Math.ceil(getRoomCount()*armorType.getFrequency());
			for(int theIndex=0;theIndex<theItemCount;++theIndex){
				do{
					theRoom = getRooms().get(Utilities.getRandom().nextInt(getRooms().size()));
				}while(!armorType.checkRoomState(theRoom.getRoomState()));
				RoomItem theItem = new RoomItem(theRoom);
				theItem.setType(ItemType.ARMOR);
				theItem.setArmorType(armorType);
				theItem.setDurability(armorType.generateDurability());
				theItem.setHidden(Utilities.rollForHiddenItem());
				theRoom.addItem(theItem);
			}
		}
		//7B - weapons
		for(WeaponType weaponType:WeaponType.values()){
			int theItemCount = (int)Math.ceil(getRoomCount()*weaponType.getFrequency());
			for(int theIndex=0;theIndex<theItemCount;++theIndex){
				do{
					theRoom = getRooms().get(Utilities.getRandom().nextInt(getRooms().size()));
				}while(!weaponType.checkRoomState(theRoom.getRoomState()));
				RoomItem theItem = new RoomItem(theRoom);
				theItem.setType(ItemType.WEAPON);
				theItem.setWeaponType(weaponType);
				theItem.setDurability(weaponType.generateDurability());
				theItem.setHidden(Utilities.rollForHiddenItem());
				theRoom.addItem(theItem);
			}
		}
		
	}

	public Room getEntrance() {
		for(int x=0;x<getWidth();++x){
			for(int y=0;y<getHeight();++y){
				Room theRoom = getRoom(x,y);
				if(theRoom.getRoomState()==RoomState.ENTRANCE){
					return theRoom;
				}
			}
		}
		return null;
	}

}
